package com.palazzisoft.railsystem.model;

import static org.apache.log4j.Logger.getLogger;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Set;

import org.apache.log4j.Logger;

/**
 * @author pablo
 * 
 */
public class RoutesManager {
	
	private final Logger log = getLogger(RoutesManager.class);
	
	private final static Long DEFAULT_SIZE = 0L;	
		
	private Net<String, Long> net;
	
	public RoutesManager(Net<String, Long> net) {
		this.net = net;
	}
	
	
	public Long distance(final String... entries) {
		Long distance = DEFAULT_SIZE;

		for (int i = 0; i < entries.length - 1; i++) {
			if (this.net.exist(entries[i], entries[i+1])) {
				distance = distance + this.net.valueOf(entries[i], entries[i+1]);
			} else {
				this.log.info("There's not such route");
				return DEFAULT_SIZE;
			}

		}

		return distance;
	}	
		
	public int routesFrom(String from, String to,int steps) {
		int count = 0;
		Set<String> adjacentsFrom = this.net.getAdjacentsFrom(from);
		
		for (int i = 0; i < steps ; i++) {
			Set<String> tempAdjacent = new HashSet<String>();
			
			for (String adjacent : adjacentsFrom) {
				if (adjacent.equals(to)) {
					count++;
				}
				
				tempAdjacent.addAll(this.net.getAdjacentsFrom(adjacent));					
			}
			
			adjacentsFrom = tempAdjacent;
		}
		
		return count;
	}

	public Long shorterDistanceFromTo(String from, String to) {
		Long[] distances = this.shorterFrom(from);
		
		if (from.equals(to)) {
			return distances[this.net.getSegmentsCounts()];
		}
		
		return distances[this.getIndexedMap().get(to)];
	}
			
	public Long[] shorterFrom(String from) {
		Long distances[] = this.buildDistance();
		boolean visitedState[] = this.buildVisisted();

		Map<String, Integer> indexedMap = this.getIndexedMap();
		distances[indexedMap.get(from)] = 0L;
		
		Queue<String> elementsToBeTestStore = new PriorityQueue<String>();
		elementsToBeTestStore.add(from);
		
		while(!elementsToBeTestStore.isEmpty()) {
			String element = elementsToBeTestStore.poll();
			
			if (!visitedState[indexedMap.get(element)]) {
				visitedState[indexedMap.get(element)] = true;
				
				for (String adjacent : this.net.getAdjacentsFrom(element)) {
					if (!visitedState[indexedMap.get(adjacent)]) {
						Long distance = this.net.valueOf(element, adjacent);
						
						if (distances[indexedMap.get(element)] + distance < distances[indexedMap.get(adjacent)] ) {
							distances[indexedMap.get(adjacent)] = distances[indexedMap.get(element)] + distance;
							elementsToBeTestStore.add(adjacent);
						}
					}
					else if (visitedState[indexedMap.get(adjacent)] && adjacent.equalsIgnoreCase(from)){
						Long distance = this.net.valueOf(element, adjacent); 
						
						if (distances[indexedMap.get(element)] + distance < distances[distances.length-1]) {
							
							distances[distances.length-1] = distances[indexedMap.get(element)] + distance;
						}
					}
				}
			}
		}
		
		return distances;
	}
	
	private Map<String, Integer> getIndexedMap() {
		Map<String, Integer> map = new HashMap<String, Integer>();
		
		int i = 0;
		for (String key : this.net.getAllSegments()) {			
			map.put(key, i);
			i++;
		}
		
		return map;
	}
	
	private boolean[] buildVisisted() {
		boolean[] visited = new boolean[this.net.getSegmentsCounts()+1];
				
		return visited;
	}	
	
	private Long[] buildDistance() {
		Long[] distances = new Long[this.net.getSegmentsCounts()+1];
		
		for (int i = 0 ; i < distances.length ; i++) {
			distances[i] = Long.valueOf(Long.MAX_VALUE);
		}
		
		return distances;
	}	
	
	public void cantidadDeRutasDesdeYHasta(String from, String to) {
		List<String> list = new ArrayList<String>();
		List<List<String>> result = new ArrayList<List<String>>();
		
		this.dfg(list, "a", "c",4, result);
		System.out.println(result.size() + " " + result);
	}
	
	public void dfg(List<String> list,String from, String to, int steps, List<List<String>> result) {
		list.add(from);
		
		if (from.equals(to) && list.size() > 1) {
			if (list.size() <= steps) {
				System.out.println(list);
				List<String> copyList = new ArrayList<String>(list);
				result.add(copyList);
			}
		}
		else {
			Set<String> adjacents = this.net.getAdjacentsFrom(from);
			for (String adjacent : adjacents) {			
					dfg(list,adjacent,to,steps,result);
			}			
		}
		list.remove(list.size()-1);
	}
	
	/*
private void findAllPaths(LinkedList path, Junction node, Junction end)
{   
    path.add(node);
    if(node == end && path.size() > 1)
    {
        System.out.println(path);
    }
    else
    {
        for(Road r : node.getAdjacencies())
        {
            if(path.size() < 30) findAllPaths(path, r.getTarget(), end);
        }
    }
}
	 */
	
}
